import { Behave, FeatureInstance, FeatureJson, FeatureType, StackItem } from '../types';
import EventBus from '../util/eventBus';
import { isEqualLatlng } from '../geo/math';
import L, { LatLng } from 'leaflet';
import { deepClone } from '../util/util';
import Feature from '../feature/feature';

type HistoryOptions = {
    getActualFeature: (id: string) => any;
    transformFeature: (type: FeatureType, id: string) => Feature | undefined;
    wait: (stackItems: StackItem[]) => void;
};

type UndoOrRedoRes = {
    oldValue: FeatureInstance | FeatureJson | undefined;
    newValue: FeatureInstance | FeatureJson | undefined;
};

class History {
    /**
     * 记录每一步修改的要素ID
     */
    private modifies: string[] = [];

    /**
     * 记录当前所处的要素索引位置
     */
    private index: number = -1;

    /**
     * 记录编辑后的撤销次数，每次编辑后重置undoNumber，确保重做的次数与编辑后撤销的次数相等
     */
    private undoNumber: number = 0;

    /**
     * 要素变更集合对象
     */
    private stacks: {
        [id: string]: {
            stackItems: StackItem[];
            index: number;
        };
    } = {};

    /**
     * 通知上层需要重新渲染的要素
     */
    private eventBus!: EventBus;

    /**
     * 栈的最大容量
     */
    private readonly max: number = 1000;

    options: HistoryOptions;

    constructor(eventBus: EventBus, options: HistoryOptions) {
        // 从缓存中恢复未保存的数据
        this.parse();
        // 回调函数
        this.eventBus = eventBus;
        this.options = options;
        // 初始化往外传递一次事件
        setTimeout(() => {
            // setTimeout 避免执行事件监听在history实例化后，第一次触发不了on事件
            this.save();
        }, 0);
    }

    parse() {
        const jsonString = localStorage.getItem('unsaved_history');

        if (!jsonString) {
            return;
        }

        const { modifies, index, stacks } = JSON.parse(jsonString);
        this.modifies = modifies || [];
        this.index = index === undefined ? -1 : index;
        this.stacks = stacks || [];
    }

    save() {
        try {
            localStorage.setItem('unsaved_history', JSON.stringify(this.json()));
        } catch {
            throw new Error('data cache is failed!');
        }
        const disableRedo = this.disableRedo();
        const disableUndo = this.disableUndo();
        this.eventBus.emit('disableDo.history', { disableRedo, disableUndo });
        this.eventBus.emit('change.history', this.getUnSavedFeatures().length);
    }

    isFull(): boolean {
        if (this.modifies.length == this.max) {
            return true;
        }

        return false;
    }

    add(behave: Behave) {
        const { annotation, features } = behave;
        const ids = Array.from(new Set(features.map((item) => item.id))).join('#');
        this.modifies.push(ids);

        features.forEach((feature) => {
            const featureJson = feature.clone();

            const stackItem: StackItem = {
                annotation,
                featureJson,
                isBase: false,
            };
            this.addStackItem(stackItem);
        });

        this.index = this.modifies.length - 1;
        this.undoNumber = 0;
        this.save();
    }

    /**
     * 添加栈内元素，history.add 和graphic.addHistory会调用
     * @param item
     */
    addStackItem(item: StackItem) {
        const id = item.featureJson.id;
        if (!this.stacks[id]) {
            this.stacks[id] = {
                stackItems: [],
                index: 0,
            };
        } else {
            this.stacks[id].stackItems = this.stacks[id].stackItems.slice(0, this.stacks[id].index + 1);
        }

        this.stacks[id].stackItems.push(item);
        this.stacks[id].index = this.stacks[id].stackItems.length - 1;
    }

    undo() {
        if (this.disableUndo()) {
            return;
        }

        const resValues: UndoOrRedoRes[] = [];
        const ids = this.modifies[this.index];
        ids.split('#').forEach((id) => {
            const resItem: UndoOrRedoRes = {
                oldValue: undefined,
                newValue: undefined,
            };
            const stack = this.stacks[id];

            if (!stack) {
                throw new Error('要素信息不完整');
            }

            const { stackItems, index } = stack;

            if (index <= 0 || index >= stackItems.length) {
                return;
            }

            if (!stackItems[index].featureJson.latLngs || stackItems[index].featureJson.latLngs.flat().length === 0) {
                resItem.oldValue = undefined;
            } else {
                resItem.oldValue = stackItems[index].featureJson;
            }
            // 前一个位置的要素
            const prev = index - 1;
            // 往上层传递深度拷贝后的值，不然会影响stacks里的值
            this.options.wait(JSON.parse(JSON.stringify([stackItems[prev]])));

            if (!stackItems[prev].featureJson.latLngs || stackItems[prev].featureJson.latLngs.flat().length === 0) {
                // 表示要删除这个要素
                resItem.newValue = undefined;
            } else {
                // 更新地图后再调用getFeatureInstance获取最新的要素
                resItem.newValue = this.getFeatureInstance(stackItems[prev].featureJson);
            }

            resValues.push(resItem);

            stack.index = prev;
        });

        this.undoNumber++;
        this.index--;
        this.save();

        this.eventBus.emit('undo', resValues);
        return resValues;
    }

    disableUndo(): boolean {
        if (this.index < 0) {
            return true;
        }

        if (this.index >= this.modifies.length) {
            return true;
        }

        return false;
    }

    redo() {
        if (this.disableRedo()) {
            return;
        }

        this.undoNumber--;
        this.index++;

        const resValues: UndoOrRedoRes[] = [];
        const ids = this.modifies[this.index];
        ids.split('#').forEach((id) => {
            const resItem: UndoOrRedoRes = {
                oldValue: undefined,
                newValue: undefined,
            };
            const stack = this.stacks[id];

            if (!stack) {
                throw new Error('要素信息不完整');
            }

            const { stackItems, index } = stack;

            if (index >= stackItems.length - 1) {
                return;
            }
            if (!stackItems[index].featureJson.latLngs || stackItems[index].featureJson.latLngs.flat().length === 0) {
                // 表示当前要素是新增的
                resItem.oldValue = undefined;
            } else {
                resItem.oldValue = stackItems[index].featureJson;
            }
            // 后一个位置
            const next = index + 1;

            this.options.wait(JSON.parse(JSON.stringify([stackItems[next]])));

            if (!stackItems[next].featureJson.latLngs || stackItems[next].featureJson.latLngs.flat().length === 0) {
                // 表示当前要素是新增的
                resItem.newValue = undefined;
            } else {
                resItem.newValue = this.getFeatureInstance(stackItems[next].featureJson);
            }

            resValues.push(resItem);

            stack.index = next;
        });

        this.save();

        this.eventBus.emit('redo', resValues);
        return resValues;
    }

    disableRedo(): boolean {
        if (this.undoNumber === 0) {
            return true;
        }
        if (this.index >= this.modifies.length - 1) {
            return true;
        }
        return false;
    }

    getFeatureInstance(json: FeatureJson): FeatureInstance | FeatureJson {
        const { type, id } = json;
        const feature = this.options.transformFeature(type, id);
        return feature ? feature.instance() : json;
    }

    reset() {
        const lastStackItems = [];

        for (let id in this.stacks) {
            const { stackItems, index } = this.stacks[id];

            if (index >= 0 && index < stackItems.length) {
                lastStackItems.push(stackItems[index]);
            }
        }

        this.eventBus.emit('notify.history', lastStackItems);
    }

    getUnSavedFeatures(): { operate: string; feature: FeatureInstance | FeatureJson }[] {
        if (this.index < 0) {
            return [];
        }
        let unSaved: { operate: string; feature: FeatureInstance | FeatureJson }[] = [];
        let operate: string = 'update';
        this.modifies.slice(0, this.index + 1).forEach((ids, index) => {
            ids.split('#').forEach((id) => {
                const stack = this.stacks[id];

                if (!stack) {
                    throw new Error('要素信息不完整');
                }

                const { stackItems, index: stackIndex } = stack;
                const baseStackItem = stackItems.find((item) => item.isBase);
                const stackItem: StackItem = deepClone(stackItems[stackIndex]);
                let { latLngs = [], type, properties } = stackItem.featureJson;

                const baseStackItemFlatLatLngs =
                    baseStackItem && baseStackItem.featureJson.latLngs ? baseStackItem.featureJson.latLngs.flat() : [];
                // 判断是否跟基类的经纬度相同，如果相同，则不计数
                const isEqualBase =
                    baseStackItem &&
                    baseStackItem.featureJson.properties === properties &&
                    latLngs.flat().length === baseStackItemFlatLatLngs.length &&
                    latLngs.flat().every((item: LatLng, itemIndex: any) => {
                        return isEqualLatlng(item, baseStackItemFlatLatLngs[itemIndex]);
                    });

                if (stackItem.isBase || isEqualBase) {
                    return;
                }

                // 点、线 latLngs都是一维数组，面是二维
                if (!latLngs || latLngs.length === 0) {
                    operate = 'delete';
                    // 删除的操作，把isBase的经纬度返回给用户
                    latLngs = baseStackItem ? baseStackItem.featureJson.latLngs : latLngs;
                } else if (id.startsWith('client')) {
                    operate = 'add';
                }

                // json对象转成经纬度对象
                if (type === 'point' || type === 'line') {
                    stackItem.featureJson.latLngs = (latLngs as LatLng[]).map((latLng) => {
                        return new L.LatLng(latLng.lat, latLng.lng);
                    });
                } else if (type === 'polygon') {
                    stackItem.featureJson.latLngs = <LatLng[][]>(latLngs as LatLng[][]).map((latLngsItem) => {
                        return latLngsItem.map((latLng) => new L.LatLng(latLng.lat, latLng.lng));
                    });
                    stackItem.featureJson.latLngs.forEach((latLngsItem, sequence) => {
                        if (!isEqualLatlng(latLngsItem[0], latLngsItem[latLngsItem.length - 1])) {
                            //面 强制首尾相连
                            (stackItem.featureJson.latLngs as LatLng[][])[sequence].push(latLngsItem[0]);
                        }
                    });
                }

                unSaved = unSaved.filter((item) => item.feature.id !== id);
                const instance = this.getFeatureInstance(stackItem.featureJson);
                unSaved.push({ operate, feature: instance });
            });
        });

        return unSaved;
    }

    contain(id: string): boolean {
        return Object.keys(this.stacks).includes(id);
    }

    clear() {
        this.modifies = [];
        this.index = -1;
        this.stacks = {};
        this.save();
    }

    json() {
        return {
            modifies: this.modifies,
            index: this.index,
            stacks: this.stacks,
        };
    }
}

export default History;
